package com.jiangwei.flv.factories;

import com.alibaba.fastjson.util.IOUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;
import org.apache.coyote.CloseNowException;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;

import javax.servlet.AsyncContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_ERROR;

/**
 * javacv转码<br/>
 * 流来源不是视频H264格式,音频AAC格式 转码为视频H264格式,音频AAC格式
 *
 * @author gc.x
 */
@Slf4j
public class ConverterTranFactories implements Converter, Runnable {
    public volatile boolean running = true;
    /**
     * 读流器
     */
    private FFmpegFrameGrabber grabber;
    /**
     * 转码器
     */
    private FFmpegFrameRecorder recorder;
    /**
     * 转FLV格式的头信息<br/>
     * 如果有第二个客户端播放首先要返回头信息
     */
    private byte[] headers;
    /**
     * 保存转换好的流
     */
    private ByteArrayOutputStream stream;
    /**
     * 流地址，h264,aac
     */
    private final String url;
    /**
     * 流输出
     */
    private final List<AsyncContext> outEntities;

    /**
     * key用于表示这个转换器
     */
    private final String key;

    /**
     * 转换队列
     */
    private final Map<String, Converter> factories;

    public ConverterTranFactories(String url, String key, Map<String, Converter> factories,
                                  List<AsyncContext> outEntities, FFmpegFrameGrabber grabber) {
        this.url = url;
        this.key = key;
        this.factories = factories;
        this.outEntities = outEntities;
        this.grabber = grabber;
    }

    @Override
    public void run() {
        try {
            log.info("this url:{} converterTranFactories start", url);
            grabber.setFrameRate(25);
            if (grabber.getImageWidth() > 1920) {
                grabber.setImageWidth(1920);
            }
            if (grabber.getImageHeight() > 1080) {
                grabber.setImageHeight(1080);
            }
            stream = new ByteArrayOutputStream();
            recorder = new FFmpegFrameRecorder(
                    stream,
                    grabber.getImageWidth(),
                    grabber.getImageHeight(),
                    grabber.getAudioChannels()
            );
            recorder.setInterleaved(true);
            recorder.setVideoOption("preset", "ultrafast");
            recorder.setVideoOption("tune", "zerolatency");
            recorder.setVideoOption("crf", "25");
            recorder.setGopSize(50);
            recorder.setFrameRate(grabber.getFrameRate());
            recorder.setSampleRate(grabber.getSampleRate());
            if (grabber.getAudioChannels() > 0) {
                recorder.setAudioChannels(grabber.getAudioChannels());
                // recorder.setAudioBitrate(grabber.getAudioBitrate());
                recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
            }
            recorder.setFormat("flv");
            recorder.setVideoBitrate(grabber.getVideoBitrate());
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
            avutil.av_log_set_level(AV_LOG_ERROR);
            recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
            recorder.start();
            if (headers == null) {
                headers = stream.toByteArray();
                stream.reset();
                writeResponse(headers);
            }
            while (running) {
                // 抓取一帧
                Frame f = grabber.grab();
                if (f != null && flag) {
                    // 转码
                    recorder.record(f);
                    if (stream.size() > 0) {
                        byte[] b = stream.toByteArray();
                        stream.reset();
                        writeResponse(b);
                        if (outEntities.isEmpty()) {
                            log.info("没有输出退出");
                            break;
                        }
                    }
                } else {
                    flag = !flag;
                    grabber = new FFmpegFrameGrabber(url);
                    grabber.setOption("rtsp_transport", "tcp");
                    grabber.setOption("stimeout", "5000000");
                    grabber.start();
                }
                TimeUnit.MILLISECONDS.sleep(5);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            closeConverter();
            completeResponse();
            log.info("this url:{} converterTranFactories exit", url);
            factories.remove(this.key);
        }
    }

    private boolean flag = true;

    public void setFlag() {
        flag = false;
    }

    /**
     * 输出FLV视频流
     */
    public void writeResponse(byte[] b) {
        Iterator<AsyncContext> it = outEntities.iterator();
        while (it.hasNext()) {
            AsyncContext o = it.next();
            try {
                o.getResponse().getOutputStream().write(b);
            } catch (ClientAbortException | CloseNowException e) {
                // log.info("移除一个输出");
                it.remove();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 退出转换
     */
    public void closeConverter() {
        IOUtils.close(grabber);
        IOUtils.close(recorder);
        IOUtils.close(stream);
    }

    /**
     * 关闭异步响应
     */
    public void completeResponse() {
        for (AsyncContext o : outEntities) {
            o.complete();
        }
    }

    @Override
    public String getKey() {
        return this.key;
    }

    @Override
    public String getUrl() {
        return this.url;
    }

    @Override
    public void addOutputStreamEntity(String key, AsyncContext entity) throws IOException {
        if (headers == null) {
            outEntities.add(entity);
        } else {
            entity.getResponse().getOutputStream().write(headers);
            entity.getResponse().getOutputStream().flush();
            outEntities.add(entity);
        }
    }

    @Override
    public void exit() {
        this.running = false;
    }

}
